﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Microsoft.SharePoint;
using RoketSolutions.SP2010.Infrostructure.Data.Repositories.Interfaces;
using RoketSolutions.SP2010.Infrostructure.Common.Extensions;
using System.Linq;

namespace RoketSolutions.SP2010.Infrostructure.Data.Repositories
{
    public static class RepositoryLocator
    {
        static readonly ReaderWriterLockSlim LockObject = new ReaderWriterLockSlim();
        static readonly Dictionary<Type, ConstructorInfo> CachedInstances = new Dictionary<Type, ConstructorInfo>();

        public static T Get<T>(String siteUrl) where T: IRepository
        {
            var repository = GetRepository<T>();
            if (repository != null)
            {
                repository.Init(siteUrl);
                return repository;
            }

            var interfaceType = typeof (T);
            throw new ArgumentOutOfRangeException( interfaceType.Name, string.Format("Repository '{0}' not found", interfaceType.Name));
        }

        public static T Get<T>(SPWeb web) where T : IRepository
        {
            var repository = GetRepository<T>();
            if (repository != null)
            {
                repository.Init(web);
                return repository;
            }

            var interfaceType = typeof(T);
            throw new ArgumentOutOfRangeException(interfaceType.Name, string.Format("Repository '{0}' not found", interfaceType.Name));
        }

        private static T GetRepository<T>() where T : IRepository
        {
            var interfaceType = typeof(T);
            if (!interfaceType.IsInterface)
            {
                throw new ArgumentException(string.Format("The specified type '{0}' is not an interface.", interfaceType.Name));
            }

            // try get registered repository
            LockObject.EnterReadLock();
            try
            {
                T repository;
                if (TryGetRepository(interfaceType, out repository))
                {
                    return repository;
                }
            }
            finally
            {
                LockObject.ExitReadLock();
            }

            // register repositories from an assembly
            LockObject.EnterWriteLock();
            try
            {
                T repository;
                if (TryGetRepository(interfaceType, out repository))
                {
                    return repository;
                }
                else
                {
                    RegisterRepositories(interfaceType.Assembly);
                    return TryGetRepository(interfaceType, out repository) ? repository : default(T);
                }
            }
            finally
            {
                LockObject.ExitWriteLock();
            }
        }

        private static bool TryGetRepository<T>(Type interfaceType, out T repository) where T : IRepository
        {
            repository = default(T);

            ConstructorInfo repositoryConstructor;
            if (CachedInstances.TryGetValue(interfaceType, out repositoryConstructor))
            {
                repository = (T)repositoryConstructor.Invoke(null);
                return true;
            }
            return false;
        }

        private static void RegisterRepositories(Assembly assembly)
        {
            var repositoryInterface = typeof(IRepository);

            // find all types that inherits from base repository interface
            var types = assembly
                .GetTypes()
                .Where(repositoryType => repositoryType.IsClass && repositoryType.IsPublic && !repositoryType.IsAbstract && repositoryInterface.IsAssignableFrom(repositoryType))
                .ToList();

            foreach (var type in types)
            {
                // try get parameterless constructor
                var constructor = type.GetConstructor(new Type[] { });

                // try get all implemented repository interfaces by the type
                var interfaces = type
                    .GetInterfaces()
                    .Where(interfaceType => interfaceType != repositoryInterface && repositoryInterface.IsAssignableFrom(interfaceType))
                    .ToList();

                foreach (var interfaceType in interfaces)
                {
                    // register repository
                    if (!CachedInstances.ContainsKey(interfaceType))
                        CachedInstances.Add(interfaceType, constructor);
                }
            }
        }
    }

}